/*
 * ShellCommand.cpp
 *
 *  Created on:  May 29, 2009
 *      Author:  Zach Riggle A37627537
 *      Project: CSE410 Project 1
 */

#include <string>
#include <sstream>
#include <stdio.h>
#include <ctype.h>
#include <pthread.h>
#include <fcntl.h>
#include <regex.h>
#include <unistd.h>
#include "ShellCommand.h"
#include "SpawnedProcess.h"
#include "Log.h"

using namespace std;

// Set the custom commands
const string ShellCommand::hist = "hist";
const string ShellCommand::cd = "cd";
const string ShellCommand::curr = "curr";
const string ShellCommand::curPID = "curPID";
const string ShellCommand::quit = "quit";

// Set the token values
const string ShellCommand::amp = "&";
const string ShellCommand::pipe = "|";
const string ShellCommand::redirStdout = ">";
const string ShellCommand::redirStdin = "<";

// Set the max length
const int ShellCommand::maxCommandLength = 128;

// The delimiters are defined as the set of all characters "><|&"
const string ShellCommand::commandDelimiters = ShellCommand::amp
        + ShellCommand::pipe + ShellCommand::redirStdout
        + ShellCommand::redirStdin;

const string ShellCommand::whitespace = " \t\n\v\f\r";

/**
 * Regular validCommandExpression to match valid command strings.  A valid command
 * consists of the following pattern, with any amount of spaces interspersed:
 *
 * 		(word)+ (ioRedirection word+)+
 *
 * That is to say, that there must be at least one 'word', and it can
 * stand alone, OR it can be followed by an IO redirection symbol, which
 * MUST be followed by another word.
 *
 * A 'word' is defined as any non-whitespace character that is also not
 * in the set of delimiters "><|&".
 *
 * IO redirection symbols are defined as a subset of the delimiters, "><|".
 *
 * Finally, a command can be optionally followed by an ampersand (see above).
 *
 * Alternately, it is valid to have a command that consists entirely of
 * whitespace.
 */
string word = "[^[:space:]" + ShellCommand::commandDelimiters + "]";
string space = "[[:space:]]*";
string nonToken = space + word + space;
string ampEnd = "([" + ShellCommand::amp + "]" + space + ")?";
string ioRedir = ShellCommand::redirStdin + ShellCommand::redirStdout
                 + ShellCommand::pipe;
string regexValidCommand = "^((" + nonToken + ")+([" + ioRedir + "]("
                           + nonToken + ")+)*" + ampEnd + "$)|(^" + space + "$)";
RegularExpression validCommand(regexValidCommand);
RegularExpression* ShellCommand::validCommandExpression_ = &validCommand;

/**
 * Regular expression to match the 'background' character -- that is, that a
 * given command is intended to be backgrounded.  This validCommandExpression was created
 * to match all strings that contain exactly one ampersand, which is followed
 * by only whitespace.
 */							  //  ^[^&]*&[[:space:]]*$
const string regexBackground = "^[^&]*&[[:space:]]*$";
//const int ShellCommand::regexBackgroundStatus = regcomp(&ShellCommand::backgroundExpression, ShellCommand::regexBackground.c_str(), REG_EXTENDED);
RegularExpression backgroundCommand(regexBackground);
RegularExpression* ShellCommand::backgroundExpression_ = &backgroundCommand;

ShellCommand::ShellCommand(string cmd, Shell* parent) {
    command = cmd;
    this->parent = parent;

    if (this->parent == NULL)
        warn("parent is null");

    backgroundNumber = 0;
    runInBackground = false;
    parseError = false;
}

string trim(string str) {
    // Trim Both leading and trailing spaces
    size_t startpos = str.find_first_not_of(ShellCommand::whitespace); // Find the first character position after excluding leading blank spaces
    size_t endpos = str.find_last_not_of(ShellCommand::whitespace); // Find the first character position from reverse af

    // if all spaces or empty return an empty string
    if ((string::npos == startpos) || (string::npos == endpos)) {
        str = "";
    } else
        str = str.substr(startpos, endpos - startpos + 1);

    return str;
}

ShellCommand::~ShellCommand() {

}

void ShellCommand::setBackgroundNumber(int newNum) {
    backgroundNumber = newNum;
}
int ShellCommand::getBackgroundNumber() {
    return backgroundNumber;
}

bool ShellCommand::isBackground() {
    return runInBackground;
}

void ShellCommand::background() {
    runInBackground = true;
    pid = fork();

    if (pid == -1) {
        error("Error during fork() while executing \"" << getCommand() << "\"");
        exit(1);
    }

    if (getPid() == 0) {
        execute();
        error("Error while executing \"" << getCommand() << "\"");
        exit(1);
    }
}

bool ShellCommand::validateCommand() {
    // Check for empty-length to save time.
    if (command.length() == 0) {
        debug ("Empty command.");
        return false;
    } else if (command.length() > ShellCommand::maxCommandLength) {
        error ("Command length greater than maximum size: " << ShellCommand::maxCommandLength);
        return false;
    }

    // Compile the regular validCommandExpression.
    if (!ShellCommand::validCommandExpression_->match(command)) {
        cout << "Syntax error." << endl;
        return false;
    }

    return true;
}
bool ShellCommand::checkBackground() {
    // Perform regex validation
    if (ShellCommand::backgroundExpression_->match(command)) {
        info("Background command regex");
        return true;
    } else {
        debug(" No background command regex match");
        return false;
    }
}

bool ShellCommand::isWhitespace(string s) {
    int firstToken = s.find_first_not_of(whitespace);

    if (firstToken == string::npos)
        return true;
    return false;
}

void ShellCommand::parseCommand() {
    int offset = 0;
    int lastOffset = 0;
    bool loop = true;
    string delimiter;
    string text;
    while (loop) {

        // Search for the next delimiter
        offset = command.find_first_of(commandDelimiters, offset);

        // If no delimiter was found, just grab the text.
        if (offset == string::npos) {
            debug("No command delimiters found in text \'" << command.substr(lastOffset) <<
                  "\' given set \'" << commandDelimiters << "\'");

            // The whole text is one contiguous command.  Don't need to loop.
            loop = false;

            // Grab the last command
            text = command.substr(lastOffset);
        }

        // If a delimiter was found, grab the delimiter *and* the text.
        else {
            // Grab the delimiter character, and the text
            delimiter = command.substr(offset, 1);
            text = command.substr(lastOffset, offset - lastOffset);

            // Check to see if the text is all whitespace.
            if (isWhitespace(text)) {
                error("Syntax error near unexpected token \'" << delimiter << "\'");
                loop = false;
                parseError = true;
                continue;
            }

            // Add the delimiter only when it was found
            delimiters.push_back(delimiter);
            debug("Found delimiter \'" << delimiters.back() << "\' at " << offset);
        }

        // Add the delimiter and text regardless of whether a delimiter was found.
        if (text.size() > 0 and !isWhitespace(text)) {
            commands.push_back(trim(text));
            debug("Found delimited text \"" << commands.back() << "\"");
        }
        // Increment the output
        offset++;
        lastOffset = offset;
    }

    // Check to make sure the numbers all match up.  There should be the same, or one more, command
    // than there are delimiters:
    // command DELIM command DELIM command [optional & delim]
    if (!(delimiters.size() == commands.size() or delimiters.size() + 1
            == commands.size())) {
        error("Syntax error.");
        parseError = true;
    }

    // Background?
    if (checkBackground())
        runInBackground = true;
}

void ShellCommand::run() {
    if (!validateCommand())
        return;

    parseCommand();
    execute();
}

int openRead(string filename) {
    filename = trim(filename);

    debug("Opening " << filename << " for reading.");
    int i = open(filename.c_str(), O_RDWR);
    if (i == -1)
        error("Could not open " << filename << " for reading.");
    return i;
}

int openWrite(string filename) {
    filename = trim(filename);

    debug("Opening " << filename << " for writing.");
    int i = open(filename.c_str(), O_WRONLY | O_TRUNC | O_CREAT, S_IRWXU);
    if (i == -1)
        error("Could not open " << filename << " for writing.");
    return i;
}

char** getArguments(string s) {
    vector<string> tokens;

    // Tokenize the string.
    int lastOffset = 0;
    int offset = 0;
    while (offset != string::npos) {
        offset = s.find_first_of(ShellCommand::whitespace, lastOffset);
        string trimmed = "";
        if (offset != string::npos)
            trimmed = trim(s.substr(lastOffset, (offset - lastOffset)));
        else
            trimmed = trim(s.substr(lastOffset));

        if (trimmed.size() > 0)
            tokens.push_back(trimmed);

        lastOffset = offset + 1;
    }

    // Create a NULL-terminated array of NULL-terminated strings.
    char **args = new char*[tokens.size() + 1];
    for (int i = 0; i != tokens.size(); i++) {
        string s = trim(tokens[i]);
        args[i] = new char[s.size() + 1];
        strncpy(args[i], s.c_str(), s.size() + 1);
    }
    args[tokens.size()] = NULL;
    tokens.clear();

    return args;
}

/**
 * Closes a file descriptor, so long as it is not STDIN or STDOUT.
 */
//void safeClose(int fd) {
//	if (fd == STDIN_FILENO || fd == STDOUT_FILENO)
//		return;
//
//	debug("Closing " << fd);
//	close(fd);
//}

void ShellCommand::execute() {
    //	warn("Not implemented.");
    //	return;

    // Any commands?
    if (commands.size() == 0)
        return;

    // Check for 'quit'
    if (commands[0].compare(ShellCommand::quit) == 0) {
        shell.quit();
        return;
    }

    // Fork here.
    this->pid = fork();
    if (pid > 0)
        return;

    vector<ChildProcess*> childProcesses;
    vector<int> listOfPipes;

    int *io = new int[2];
    int *in = &(io[0]);
    int *out = &(io[1]);

    int pipes[2] = { 0, 1 };
    int oldpipes[2] = { 0, 1 };
    int ancientpipes[2] = { 0, 1 };
    ::pipe(pipes);

    const int stdin = STDIN_FILENO;
    const int stdout = STDOUT_FILENO;

    // Set to 'true' to have STDIN on the next command set to the pipe.
    bool pipeNext = false;

    for (int i = 0; i < commands.size(); i++) {
        // Get the command
        string c = commands[i];

        // Create the new pipes for this run
        debug(c << " pipes " << pipes[0] << "," << pipes[1]);
        listOfPipes.push_back(pipes[0]);
        listOfPipes.push_back(pipes[1]);

        // Reset the IO descriptors
        *in = stdin;
        *out = stdout;
        if (pipeNext) {

            *in = oldpipes[stdin];
            debug("Piping STDIN from " << *in);
            pipeNext = false;
        }
        /*
         * Redirect IO.  Note that subscripts are PURPOSEFULLY used instead of
         * iterators here, in order to use the loop variable 'i' and to also
         * increment it at the end of the loop.  Note that this will cause the
         * last redirection to be used.
         *
         * Example (use Bash):
         * 	echo 123 > file1
         * 	echo abc > file2
         * 	cat < file1 < file2
         * 	>>> abc
         *
         * Additional information:
         * Multiple output redirections will create multiple files.  This is
         * proper behavior.
         *
         * Example (use Bash):
         * echo 123 > file1 > file2 > file3
         */
        while (delimiters.size() > i && commands.size() > (i + 1)) {

            // Redirect STDIN from a file
            if (delimiters[i].compare(redirStdin) == 0) {
                safeClose(*in);
                if ((*in = openRead(commands[i + 1])) == -1)
                    exit(-1);
                else
                    info("Redirected STDIN to " << commands[i+1] << " (" << *in << ")");
            }

            // Redirect STDOUT to a file
            if (delimiters[i].compare(redirStdout) == 0) {
                safeClose(*out);
                if ((*out = openWrite(commands[i + 1])) == -1)
                    exit(-1);
                else
                    info("Redirected STDOUT to " << commands[i+1] << " (" << *out << ")");
            }

            // Redirect STDOUT to a pipe.  Note that we must break on pipe.  Note that
            // we only set the pipe if we have not previously defined a file redirection.
            if (delimiters[i].compare(pipe) == 0) {
                if (*out == stdout) {
                    info("Piping STDOUT to " << pipes[stdout]);
                    pipeNext = true;
                    safeClose(*out);
                    *out = pipes[stdout];
                } else {
                    info("Ignoring pipe");
                }
                break;
            }

            // If we got '<' or '>' we want to skip the next 'command'
            // because it is really a filename.  Also, we want to check
            // for additional IO redirection.
            i++;
        }

        // Okay, all of the I/O redirection is set up, and ready to be opened.
        char **arguments = getArguments(c);
        SpawnedProcess *p = new SpawnedProcess();
        p->redirect(io);
        p->setPipesToClose(oldpipes, pipes);
        p->execute(arguments);

        //		ChildProcess *p = new ChildProcess(io, oldpipes, pipes, arguments[0],
        //				arguments);
        childProcesses.push_back(p);

        // Close off ancient pipes that are no longer needed.
        safeClose(oldpipes[0]);
        safeClose(oldpipes[1]);

        // Save the pipes from the last run.  These will be needed for IO redirection, and
        // to close the handles.
        oldpipes[0] = pipes[0];
        oldpipes[1] = pipes[1];

        // Create new pipes.
        ::pipe(pipes);
    }
    // Close the pipes.  All of them.
    for (int i = 0; i < listOfPipes.size(); i++)
        safeClose(listOfPipes[i]);

    // Collection.
    returnValue = 0;
    for (int i = 0; i < childProcesses.size(); i++) {
        debug("Waiting on childProcesses[" << i << "]");
        childProcesses[i]->wait();
        if (returnValue == 0 && childProcesses[i]->getReturnValue() != 0)
            returnValue = childProcesses[i]->getReturnValue();
        delete childProcesses[i];
    }

    // Die
    info("Exiting with return value: " << returnValue);
    exit(returnValue);
}

const string ShellCommand::getCommand() {
    return command;
}
